////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2003-2008 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
package mx.geom
{
	//import __AS3__.vec.Vector;
	
	import flash.events.Event;
	import flash.geom.Matrix;
	import flash.geom.Matrix3D;
	import flash.geom.Point;
	import flash.geom.Vector3D;
	
	import mx.core.AdvancedLayoutFeatures;
	import mx.utils.MatrixUtil;

	/**
	 *  A CompoundTransform represents a 2D or 3D matrix transform.  A compound transform represents a matrix that can be queried or set either as a 2D matrix,
	 *  a 3D matrix, or as individual convenience transform properties such as x, y, scaleX, rotationZ, etc. 
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public class CompoundTransform
	{
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
		public function CompoundTransform()
		{
		}
		
			
	
    /**
     * @private
     * storage for transform properties. These values are concatenated together with the layout properties to
     * form the actual computed matrix used to render the object.
     */
	private var _rotationX:Number = 0;
	private var _rotationY:Number = 0;
	private var _rotationZ:Number = 0;
	private var _scaleX:Number = 1;
	private var _scaleY:Number = 1;
	private var _scaleZ:Number = 1;		
	private var _x:Number = 0;
	private var _y:Number = 0;
	private var _z:Number = 0;
	
	private var _transformX:Number = 0;
	private var _transformY:Number = 0;
	private var _transformZ:Number = 0;
		
	
    /**
     * @private
     * slots for the 2D and 3D matrix transforms.  Note that 
     * these are only allocated and computed on demand -- many component instances will never use a 3D
     * matrix, for example. 
     */
	private var _matrix:Matrix;
	private var _matrix3D:Matrix3D;
	
	
    /**
     * @private
     * bit field flags for indicating which transforms are valid -- the layout properties, the matrices,
     * and the 3D matrices.  Since developers can set any of the three programmatically, the last one set
     * will always be valid, and the others will be invalid until validated on demand.
     */
	private static const MATRIX_VALID:uint		= 0x20;
	private static const MATRIX3D_VALID:uint		= 0x40;
	private static const PROPERTIES_VALID:uint 	= 0x80;
	
	
    /**
     * @private
     * flags for tracking whether the  transform is 3D. A transform is 3D if any of the 3D properties -- rotationX/Y, scaleZ, or z -- are set.
     */
	private static const IS_3D:uint 				= 0x200;
	private static const M3D_FLAGS_VALID:uint			= 0x400;
	 			
    /**
     * @private
     * constants to indicate which form of a transform -- the properties, matrix, or matrix3D -- is
     *  'the source of truth.'   
     */
	public static const SOURCE_PROPERTIES:uint			= 1;
	public static const SOURCE_MATRIX:uint 			= 2;
	public static const SOURCE_MATRIX3D:uint 			= 3;
	
    /**
     * @private
     * indicates the 'source of truth' for the transform.  
     */
	public var sourceOfTruth:uint = SOURCE_PROPERTIES;
	
    /**
     * @private
     * general storage for all of ur flags.  
     */
	private var _flags:uint =  PROPERTIES_VALID;
	
    /**
     * @private
     * flags that get passed to the invalidate method indicating why the invalidation is happening.
     */
	private static const INVALIDATE_FROM_NONE:uint = 			0;						
	private static const INVALIDATE_FROM_PROPERTY:uint = 		4;						
	private static const INVALIDATE_FROM_MATRIX:uint = 			5;						
	private static const INVALIDATE_FROM_MATRIX3D:uint = 		6;						

	/**
	 * @private
	 * static data used by utility methods below
	 */
	private static var decomposition:Vector.<Number> = new Vector.<Number>();
	decomposition.push(0);
	decomposition.push(0);
	decomposition.push(0);
	decomposition.push(0);
	decomposition.push(0);

	private static const RADIANS_PER_DEGREES:Number = Math.PI / 180;

	//----------------------------------------------------------------------------
	
	/**
	 *  The x value of the transform.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set x(value:Number):void
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _x)
			return;
		translateBy(value-_x,0,0);
		invalidate(INVALIDATE_FROM_NONE,false);
	}
    /**
     * @private
     */
	public function get x():Number
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		return _x;
	}
	
	/**
	 *  The y value of the transform.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set y(value:Number):void
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _y)
			return;
		translateBy(0,value-_y,0);
		invalidate(INVALIDATE_FROM_NONE,false);
	}
	
    /**
     * @private
     */
	public function get y():Number
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		return _y;
	}
	
	/**
	 *  The z value of the transform.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set z(value:Number):void
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _z)
			return;
		translateBy(0,0,value-_z);
		invalidate(INVALIDATE_FROM_NONE,true);
	}
	
    /**
     * @private
     */
	public function get z():Number
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		return _z;
	}
	
	//------------------------------------------------------------------------------

	
	/**
	 *  The rotationX, in degrees, of the transform.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set rotationX(value:Number):void
	{
	    // clamp the rotation value between -180 and 180.  This is what 
        // the Flash player does, so let's mimic it here too.
		value = MatrixUtil.clampRotation(value);
            
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _rotationX)
			return;
		_rotationX = value;
		invalidate(INVALIDATE_FROM_PROPERTY,true);
	}
	
    /**
     * @private
     */
	public function get rotationX():Number
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		return _rotationX;
	}
	
	/**
	 *  The rotationY, in degrees, of the transform.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set rotationY(value:Number):void
	{
	    // clamp the rotation value between -180 and 180.  This is what 
        // the Flash player does, so let's mimic it here too.
		value = MatrixUtil.clampRotation(value);
        
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _rotationY)
			return;
		_rotationY = value;
		invalidate(INVALIDATE_FROM_PROPERTY,true);
	}
	
    /**
     * @private
     */
	public function get rotationY():Number
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		return _rotationY;
	}
	
	/**
	 *  The rotationZ, in degrees, of the transform.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set rotationZ(value:Number):void
	{
	    // clamp the rotation value between -180 and 180.  This is what 
        // the Flash player does, so let's mimic it here too.
		value = MatrixUtil.clampRotation(value);
        
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _rotationZ)
			return;
		_rotationZ = value;
		invalidate(INVALIDATE_FROM_PROPERTY,false);
	}
	
    /**
     * @private
     */
	public function get rotationZ():Number
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		return _rotationZ;
	}
	
	//------------------------------------------------------------------------------
	
	
	/**
	 *  The scaleX of the transform.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set scaleX(value:Number):void
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _scaleX)
			return;
		_scaleX = value;
		invalidate(INVALIDATE_FROM_PROPERTY,false);
	}
	
    /**
     * @private
     */
	public function get scaleX():Number
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		return _scaleX;
	}
	
	/**
	 *  The scaleY of the transform.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set scaleY(value:Number):void
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _scaleY)
			return;
		_scaleY = value;
		invalidate(INVALIDATE_FROM_PROPERTY,false);
	}
	
    /**
     * @private
     */
	public function get scaleY():Number
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		return _scaleY;
	}
	
	
	/**
	 *  The scaleZ of the transform.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set scaleZ(value:Number):void
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _scaleZ)
			return;
		_scaleZ = value;
		invalidate(INVALIDATE_FROM_PROPERTY,true);
	}
	
    /**
     * @private
     */
	public function get scaleZ():Number
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		return _scaleZ;
	}
	

	/**
	 * @private
	 * returns true if the transform has 3D values.
	 */
	public function get is3D():Boolean
	{
		if ((_flags & M3D_FLAGS_VALID) == 0)
			update3DFlags();
		return ((_flags & IS_3D) != 0);
	}
	
	//------------------------------------------------------------------------------
	/**
	 *  The x value of the transform center. The transform center is kept fixed as rotation and scale are applied. 
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set transformX(value:Number):void
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _transformX)
			return;
		_transformX = value;
		invalidate(INVALIDATE_FROM_PROPERTY,true);
	}

    /**
     * @private
     */
	public function get transformX():Number
	{
		return _transformX;
	}

	//------------------------------------------------------------------------------
	/**
	 *  The y value of the tansform center. The transform center is kept fixed as rotation and scale are applied. 
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set transformY(value:Number):void
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _transformY)
			return;
		_transformY = value;
		invalidate(INVALIDATE_FROM_PROPERTY,true);
	}
	
    /**
     * @private
     */
	public function get transformY():Number
	{
		return _transformY;
	}
	//------------------------------------------------------------------------------
	/**
	 *  The z value of the tansform center. The transform center is kept fixed as rotation and scale are applied. 
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function set transformZ(value:Number):void
	{
		if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix();
		if (value == _transformZ)
			return;
		_transformZ = value;
		invalidate(INVALIDATE_FROM_PROPERTY,true);
	}
	
    /**
     * @private
     */
	public function get transformZ():Number
	{
		return _transformZ;
	}

	//------------------------------------------------------------------------------
	
	/**
	 * @private
	 * invalidates our various cached values.  Any change to the CompoundTransform object that affects
	 * the various transforms should call this function. 
     * @param reason - the code indicating what changes to cause the invalidation.
     * @param affects3D - a flag indicating whether the change affects the 2D/3D nature of the various transforms.
     * @param dispatchChangeEvent - if true, the CompoundTransform will dispatch a change indicating that its underlying transforms
     * have been modified. 
  	 */
	private function invalidate(reason:uint,affects3D:Boolean):void
	{
		//race("invalidating: " + reason);
		switch(reason)
		{
			case INVALIDATE_FROM_PROPERTY:
				sourceOfTruth = SOURCE_PROPERTIES;
				_flags |= PROPERTIES_VALID;
				_flags &= ~MATRIX_VALID;
				_flags &= ~MATRIX3D_VALID;
				break;
			case INVALIDATE_FROM_MATRIX:
				sourceOfTruth = SOURCE_MATRIX;
				_flags |= MATRIX_VALID;
				_flags &= ~PROPERTIES_VALID;
				_flags &= ~MATRIX3D_VALID;
				break;
			case INVALIDATE_FROM_MATRIX3D:
				sourceOfTruth = SOURCE_MATRIX3D;
				_flags |= MATRIX3D_VALID;
				_flags &= ~PROPERTIES_VALID;
				_flags &= ~MATRIX_VALID;
				break;
		}						
		if (affects3D)
			_flags &= ~M3D_FLAGS_VALID;
			
	}
	
	private static const EPSILON:Number = .001;
	/**
	 * @private
	 * updates the flags that indicate whether the layout, offset, and/or computed transforms are 3D in nature.  
	 * Since the user can set either the individual transform properties or the matrices directly, we compute these 
	 * flags based on what the current 'source of truth' is for each of these values.
  	 */
	private function update3DFlags():void
	{			
		if ((_flags & M3D_FLAGS_VALID) == 0)
		{
			var matrixIs3D:Boolean = false;

			switch(sourceOfTruth)
			{
				case SOURCE_PROPERTIES:
					matrixIs3D = ( // note that rotationZ is the same as rotation, and not a 3D affecting							
					  	(Math.abs(_scaleZ-1) > EPSILON) ||  // property.
					  	((Math.abs(_rotationX)+EPSILON)%360) > 2*EPSILON ||
					  	((Math.abs(_rotationY)+EPSILON)%360) > 2*EPSILON ||
					  	Math.abs(_z) > EPSILON
					  	);
					break;
				case SOURCE_MATRIX:
					matrixIs3D = false;
					break;
				case SOURCE_MATRIX3D:
					var rawData:Vector.<Number> = _matrix3D.rawData;					
					matrixIs3D = (rawData[2] != 0 ||		// rotation y 
									rawData[6] != 0 || 		// rotation x
									rawData[8] !=0 || 		// rotation y
									rawData[10] != 1 || 	// scalez / rotation x / rotation y
									rawData[14] != 0);		// translation z
					break;								
			}

			if (matrixIs3D)
				_flags |= IS_3D;
			else
				_flags &= ~IS_3D;
				
			_flags |= M3D_FLAGS_VALID;
		}
	}
	
	
	/** 
    *  Applies the delta to the transform's translation component. Unlike setting the x, y, or z properties directly,
	*  this method can be safely called without changing the transform's concept of 'the source of truth'.
	*  
	*  @langversion 3.0
	*  @playerversion Flash 9
	*  @playerversion AIR 1.1
	*  @productversion Flex 3
	*/
	public function translateBy(x:Number,y:Number,z:Number = 0):void
	{
		if (_flags & MATRIX_VALID)
		{
			_matrix.tx += x;
			_matrix.ty += y;
		}
		if (_flags & PROPERTIES_VALID)
		{
			_x += x;
			_y += y;
			_z += z;
		}
		if (_flags & MATRIX3D_VALID)
		{
			var data:Vector.<Number> = _matrix3D.rawData;
			data[12] += x;
			data[13] += y;
			data[14] += z;
			_matrix3D.rawData = data;			
		}	
		invalidate(INVALIDATE_FROM_NONE,z != 0);
	}
	

	/**
	 *  The 2D matrix either set directly by the user, or composed by combining the transform center, scale, rotation
	 *  and translation, in that order.  
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function get matrix():Matrix
	{
		
		if (_flags & MATRIX_VALID)
			return _matrix;
			
		if ((_flags & PROPERTIES_VALID) == false)
			validatePropertiesFromMatrix();
			
		var m:Matrix = _matrix;
		if (m == null)
			m = _matrix = new Matrix();
		else
			m.identity();
	
		AdvancedLayoutFeatures.build2DMatrix(m,_transformX,_transformY,
					  _scaleX,_scaleY,
					  _rotationZ,
					  _x,_y);	
		_flags |= MATRIX_VALID;
		return m;		
	}
		
	/**
	 * @private
	 */	
	public function set matrix(v:Matrix):void
	{
		if (_matrix== null)
		{
			_matrix = v.clone();
		}
		else
		{
			_matrix.identity();	
			_matrix.concat(v);			
		}
		invalidate(INVALIDATE_FROM_MATRIX,false);
	}
	
	/**
	 * @private
	 * decomposes the offset transform matrices down into the convenience offset properties. Note that this is not
	 * a bi-directional transformation -- it is possible to create a matrix that can't be fully represented in the
	 * convenience properties. This function will pull from the matrix or matrix3D values, depending on which was most
	 * recently set
	 */
	private function validatePropertiesFromMatrix():void
	{	    
	    if (sourceOfTruth == SOURCE_MATRIX3D)
	    {
	    	var result:Vector.<Vector3D> = _matrix3D.decompose();
	    	_rotationX = result[1].x / RADIANS_PER_DEGREES;
	    	_rotationY = result[1].y / RADIANS_PER_DEGREES;
	    	_rotationZ = result[1].z / RADIANS_PER_DEGREES;
	    	_scaleX = result[2].x;
	    	_scaleY = result[2].y;
	    	_scaleZ = result[2].z;

			if (_transformX != 0 || _transformY != 0 || _transformZ != 0)
			{
				var postTransformTCenter:Vector3D = _matrix3D.transformVector(new Vector3D(_transformX,_transformY,_transformZ));
		    	_x = postTransformTCenter.x - _transformX;
	    		_y = postTransformTCenter.y - _transformY;
	    		_z = postTransformTCenter.z - _transformZ;
			}
			else
			{
		    	_x = result[0].x;
		    	_y = result[0].y;
	    		_z = result[0].z;
	  		}
	    }                        
	    else if (sourceOfTruth == SOURCE_MATRIX)
	    {
	    	MatrixUtil.decomposeMatrix(decomposition,_matrix,_transformX,_transformY);
	    	_x = decomposition[0];
	    	_y = decomposition[1];
	    	_z = 0;
	    	_rotationX = 0;
	    	_rotationY = 0;
	    	_rotationZ = decomposition[2];
	    	_scaleX = decomposition[3];
	    	_scaleY = decomposition[4];
	    	_scaleZ = 1;
	    }
	    _flags |= PROPERTIES_VALID;
		
	}
	
	
	
	/**
	 *  The 3D matrix either set directly by the user, or composed by combining the transform center, scale, rotation
	 *  and translation, in that order. 
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function get matrix3D():Matrix3D
	{
		if (_flags & MATRIX3D_VALID)
			return _matrix3D;
		
		if ((_flags & PROPERTIES_VALID) == false)
			validatePropertiesFromMatrix();
							
		var m:Matrix3D = _matrix3D;
		if (m == null)
			m =  _matrix3D = new Matrix3D();
		else
			m.identity();
		
			AdvancedLayoutFeatures.build3DMatrix(m,transformX,transformY,transformZ,
					  _scaleX,_scaleY,_scaleZ,
					  _rotationX,_rotationY,_rotationZ,						  
					  _x,_y,_z);
		_flags |= MATRIX3D_VALID;
		return m;
		
	}
	
	/**
	 * @private
	 */
	public function set matrix3D(v:Matrix3D):void
	{
		if (_matrix3D == null)
		{
			_matrix3D = v.clone();
		}
		else
		{
			_matrix3D.identity();	
			_matrix3D.append(v);			
		}
		invalidate(INVALIDATE_FROM_MATRIX3D,true);
	}

}
}
